package away3d.containers ;


import away3d.core.base.* ;
import away3d.core.partition.Partition3D ;
import away3d.events.Scene3DEvent ;
import away3d.library.assets.AssetType ;
import away3d.library.assets.IAsset ;
import flash.events.Event ;
import flash.geom.Matrix3D ;
import flash.geom.Vector3D ;



/**
 * ObjectContainer3D is the most basic scene graph node. It can contain other ObjectContainer3Ds.
 *
 * ObjectContainer3D can have its own scene partition assigned. However, when assigned to a different scene,
 * it will loose any partition information, since partitions are tied to a scene.
 *
 * TODO: polycount updates
 * TODO: all the event-based stuff is not done ( onDimensionsUpdate etc ) Trying to avoid bubbling here :s
 * TODO: names not implemented yet ( will be related too closely to a Library )
 * TODO: pivot stuff --> pass pivot point to appendRotation
 *
 */
class ObjectContainer3D extends Object3D implements IAsset
{
    
    
    private var _children                   : Vector<ObjectContainer3D> ;
    private var _scene                      : Scene3D ;
    private var _oldScene                   : Scene3D ;
    private var _parent                     : ObjectContainer3D ;
    private  var _sceneTransform            : Matrix3D ;
    private  var _sceneTransformDirty       : Bool ;
    private var _inverseSceneTransform      : Matrix3D ;
    private var _inverseSceneTransformDirty : Bool ;
    private var _scenePosition              : Vector3D ;
    private var _scenePositionDirty         : Bool ;
    
    // _explicitPartition is what the user explicitly set as the partition
    // implicitPartition is what is inherited from the parents if it doesn't have its own explicitPartition
    // this allows not having to traverse the scene graph to figure out what partition is set
    private  var _explicitPartition         : Partition3D ;
    private  var _implicitPartition         : Partition3D ;
    
    private var _explicitVisibility         : Bool ;
    
    // visibility passed on from parents
    private var _implicitVisibility         : Bool ;
    
    
    
    /**
     * Creates a new ObjectContainer3D object.
     */
    public function ObjectContainer3D()
    {
        
        super() ;
        _sceneTransform                 = new Matrix3D() ;
        _sceneTransformDirty            = true ;
        _inverseSceneTransform          = new Matrix3D() ;
        _inverseSceneTransformDirty     = true ;
        _scenePosition                  = new Vector3D() ;
        _scenePositionDirty             = true ;
        _explicitVisibility             = true ;
        _implicitVisibility             = true ;
        _children                       = new Vector() ;
        
    }
    
    
    public var visible( get_visible, set_visible ) : Bool ;
    
    
    private function get_visible() : Bool
    {
        
        return _explicitVisibility ;
        
    }
    
    
    private function set_visible( value : Bool ) : Bool
    {
        
        var len : uint = _children.length ;
        
        _explicitVisibility = value ;
        
        for ( i in 0...len ) 
        {
            //TODO: check this line not sure we can get children like that
            _children[ i ]._implicitVisibility = _explicitVisibility && _implicitVisibility ;
            
        }
        
        return value ;
        
    }
    
    
    public var isVisible( get_isVisible, null ): Bool ;
    
    
    public function get_isVisible() : Bool
    {
        
        return _implicitVisibility && _explicitVisibility ;
        
    }
    
    
    public var assetType( get_assetType, null ): String ;
    
    
    private function get_assetType() : String
    {
        
        return AssetType.CONTAINER ;
        
    }
    
    
    public var scenePosition( get_scenePosition, null ): Vector3D ;
    
    
    /**
     * The global position of the ObjectContainer3D in the scene.
     */
    private function get_scenePosition() : Vector3D
    {
        if( _scenePositionDirty )
        {
            
            sceneTransform.copyRowTo( 3, _scenePosition ) ;
            _scenePositionDirty = false ;
            
        }
        return _scenePosition ;
        
    }
    
    
    public var minX( get_minX, null ): Float ;
    
    
    /**
     * The minimum extremum of the object along the X-axis.
     */
    private function get_minX() : Float
    {
        
        var i   : Int  ;
        var len = _children.length ;
        //TODO: change this line!!
        var min = Number.POSITIVE_INFINITY ;
        var m   : Float ;
        
        while( i < len )
        {
            //TODO: Think this needs to change from array access
            m = _children[ i++ ].minX ;
            if( m < min ) min = m ;
            
        }
        
        return min ;
        
    }
    
    
    public var minY( get_minY, null ): Float ;
    
    
    /**
     * The minimum extremum of the object along the Y-axis.
     */
    private function get_minY() : Float
    {
        var i   : Int  ;
        var len = _children.length ;
        //TODO: change this line!!
        var min = Number.POSITIVE_INFINITY ;
        var m   : Float ;
        
        while( i < len )
        {
            //TODO: Think this needs to change from array access
            m = _children[ i++ ].minY ;
            if( m < min ) min = m ;
            
        }
        
        return min ;
        
    }
    
    
    public var minZ( get_minZ, null ): Float ;
    
    
    /**
     * The minimum extremum of the object along the Z-axis.
     */
    private function get_minZ() : Float
    {
        
        var i   : Int  ;
        var len = _children.length ;
        //TODO: change this line!!
        var min = Number.POSITIVE_INFINITY ;
        var m   : Float ;
        
        while( i < len )
        {
            
            //TODO: Think this needs to change from array access
            m = _children[ i++ ].minZ ;
            if( m < min ) min = m ;
            
        }
        
        return min ;
        
    }
    
    
    public var maxX( get_maxX, null ): Float ;
    
    
    /**
     * The maximum extremum of the object along the X-axis.
     */
    private function get_maxX() : Float
    {
        // todo: this isn't right, doesn't take into account transforms
        var i   : Int  ;
        var len = _children.length ;
        //TODO: change this line!!
        var max = Number.NEGATIVE_INFINITY ;
        var m   : Float ;
        
        while( i < len )
        {
            //TODO: Think this needs to change from array access
            m = _children[ i++ ].maxX ;
            if( m > max ) max = m ;
            
        }
        return max ;
    }
    
    
    public var maxY( get_maxY, null ): Float ;
    
    
    /**
     * The maximum extremum of the object along the Y-axis.
     */
    private function get_maxY() : Float
    {
        
        var i   : Int  ;
        var len = _children.length ;
        //TODO: change this line!!
        var max = Float.NEGATIVE_INFINITY ;
        var m   : Float ;
        
        while( i < len )
        {
            //TODO: Think this needs to change from array access
            m = _children[ i++ ].maxY ;
            if( m > max ) max = m ;
            
        }
        
        return max ;
        
    }
    
    
    public var maxZ( get_maxZ, null ): Float ;
    
    
    /**
     * The maximum extremum of the object along the Z-axis.
     */
    private function get_maxZ() : Float
    {
        
        var i   : Int  ;
        var len = _children.length ;
        //TODO: change this line!!
        var max = Float.NEGATIVE_INFINITY ;
        var m   : Float ;
        
        while( i < len )
        {
            //TODO: Think this needs to change from array access
            m = _children[ i++ ].maxZ ;
            if( m > max ) max = m ;
            
        }
        
        return max ;
        
    }
    
    
    public var partition( get_partition, set_partition ): Partition3D ;
    
    
    /**
     * The space partition to be used by the object container and all its recursive children, unless it has its own
     * space partition assigned.
     */
    private function get_partition() : Partition3D
    {
        
        return _explicitPartition ;
        
    }
    
    
    private function set_partition( value : Partition3D ) : Partition3D
    {
        
        _explicitPartition = value ;
        
        //TODO: sort out what this rather smart code is doing... meh!
        implicitPartition = value     ? value :
            _parent    ? parent.implicitPartition
            : null ;
            
        return value ;
        
    }
    
    
    public var implicitPartition( get_implicitPartition, set_implicitPartition ): Partition3D ;
    
    
    /**
     * The space partition used for this object, possibly inherited from its parent.
     */
    private function get_implicitPartition() : Partition3D
    {
        
        return _implicitPartition ;
        
    }
    
    
    private function set_implicitPartition( value : Partition3D )
    {
        
        if( value == _implicitPartition ) return value ;
        
        var i               : Int  ;
        var len             = _children.length ;
        var child           : ObjectContainer3D ;
        _implicitPartition  = value ;
        
        while ( i < len )
        {
            //TODO: check array access is valid here!! 
            child = _children[ i++ ] ;
            // assign implicit partition if no explicit one is given
            if( !child._explicitPartition ) child.implicitPartition = value ;
            
        }
        
        return value ;
        
    }
    
    
    /**
     * The local transformation matrix that transforms to the parent object's space.
     * @param value
     */
    override private function set_transform( value : Matrix3D )
    {
        
        super.transform = value ;
        invalidateSceneTransform() ;
        
    }
    
    
    public var sceneTransform( get_sceneTransform, null ) : Matrix3D ;
    
    
    /**
     * The transformation matrix that transforms from model to world space.
     */
    private function get_sceneTransform() : Matrix3D
    {
        
        if( _sceneTransformDirty ) updateSceneTransform() ;
        return _sceneTransform ;
        
    }
    
    
    public var inverseSceneTransform( get_inverseSceneTransform, null ) : Matrix3D ;
    
    
    /**
     * The inverse scene transform object that transforms from world to model space.
     */
    private function get_inverseSceneTransform() : Matrix3D
    {
        
        if( _inverseSceneTransformDirty )
        {
            
            _inverseSceneTransform.copyFrom( sceneTransform ) ;
            _inverseSceneTransform.invert() ;
            _inverseSceneTransformDirty = false ;
            
        }
        return _inverseSceneTransform ;
    }
    
    
    public var parent( get_parent, set_parent ): ObjectContainer3D ;
    
    
    /**
     * The parent ObjectContainer3D to which this object's transformation is relative.
     */
    private function get_parent() : ObjectContainer3D
    {
        
        return _parent ;
        
    }
    
    
    private function set_Parent( value : ObjectContainer3D )
    {
        
        _parent = value ;
        
        if( value == null )
        {
            
            scene = null ;
            return value ;
            
        }
        
        invalidateSceneTransform() ;
        return value ;
        
    }
    
    
    /**
     * Adds a child ObjectContainer3D to the current object. The child's transformation will become relative to the
     * current object's transformation.
     * @param child The object to be added as a child.
     * @return A reference to the added child object.
     */
    public function addChild( child : ObjectContainer3D ) : ObjectContainer3D
    {
        
        if( child == null ) throw new Error( "Parameter child cannot be null." ) ;
        if( !child._explicitPartition ) child.implicitPartition = _implicitPartition ;
        
        child._parent   = this ;
        child.scene     = _scene ;
        child.invalidateSceneTransform() ;
        
        
        _children.push( child ) ;
        return child ;
        
    }
    
    
    /**
     * Adds an array of 3d objects to the scene as children of the container
     *
     * @param    ...childarray        An array of 3d objects to be added
     */
    public function addChildren( childarray: Array<ObjectContainer3D )
    {
        
        for( child in childarray )
        {
            
            addChild( child ) ;
            
        }
        
    }
    
    
    /**
     * Removes a 3d object from the child array of the container
     *
     * @param    child    The 3d object to be removed
     * @throws    Error    ObjectContainer3D.removeChild( null )
     */
    public function removeChild( child: ObjectContainer3D )
    {
        
        if( child == null ) throw new Error( "Parameter child cannot be null" ) ;
        
        var childIndex = _children.indexOf( child ) ;
        
        if( childIndex == -1 ) throw new Error( "Parameter is not a child of the caller" ) ;
        
        // index is important because getChildAt needs to be regular.
        _children.splice( childIndex, 1 ) ;
        
        // this needs to be nullified before the callbacks!
        child.setParent( null ) ;
        if( !child._explicitPartition ) child.implicitPartition = null ;
        
    }
    
    /**
     * Retrieves the child object at the given index.
     * @param index The index of the object to be retrieved.
     * @return The child object at the given index.
     */
    private function get_ChildAt( index : Int ) : ObjectContainer3D
    {
        //TODO: check this is valid..
        return _children[ index ] ;
        
    }
    
    
    /**
     * The amount of child objects of the ObjectContainer3D.
     */
    private function get_numChildren() : Int
    {
        
        return _children.length ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override public function lookAt( target: Vector3D, ?upAxis: Vector3D = null )
    {
        
        super.lookAt( target, upAxis ) ;
        invalidateSceneTransform() ;
        
    }
    
    
    override public function translateLocal( axis : Vector3D, distance : Float )
    {
        
        super.translateLocal( axis, distance ) ;
        invalidateSceneTransform() ;
        
    }
    
    /**
     * @inheritDoc
     */
    override public function dispose( deep : Bool )
    {
        
        if( parent ) parent.removeChild( this ) ;
        
        if( deep )
        {
            
            for( i in 0..._children.length )
            {
                
                //TODO: check this is valid.. replace
                _children[i].dispose( true ) ;
                
            }
            
        }
    }
    
    
    public var scene( get_scene, set_scene ): Scene3D ;
    
    
    /**
     * A reference to the Scene3D object to which this object belongs.
     */
    public function get_scene() : Scene3D
    {
        
        return _scene ;
        
    }
    
    
    public function set_scene( value : Scene3D )
    {
        
        var i   : Int  ;
        var len = _children.length ;
        
        //TODO: check valid... replace
        while ( i < len ) _children[i++].scene = value ;
        
        if( _scene == value ) return value;
        
        // test to see if we're switching roots while we're already using a scene partition
        if( value == null )
        {
            
            _oldScene = _scene ;
            
        }
        
        if( _explicitPartition && _oldScene && _oldScene != _scene )
        {
            
            partition = null ;
            
        }
        
        if( value ) _oldScene = null ;
        // end of stupid partition test code
        
        _scene = value ;
        
        if( _scene )
        {
            
            _scene.dispatchEvent( new Scene3DEvent( Scene3DEvent.ADDED_TO_SCENE, this ) ) ;
            
        }
        else if( _oldScene )
        {
            
            _oldScene.dispatchEvent( new Scene3DEvent( Scene3DEvent.REMOVED_FROM_SCENE, this ) ) ;
            
        }
        
    }
    
    
    /**
     * @inheritDoc
     */
    override private function invalidateTransform()
    {
        
        super.invalidateTransform() ;
        invalidateSceneTransform() ;
        
    }
    
    
    /**
     * Invalidates the scene transformation matrix, causing it to be updated the next time it's requested.
     */
    private function invalidateSceneTransform()
    {
        
        _scenePositionDirty = true ;
        _inverseSceneTransformDirty = true ;
        
        if( _sceneTransformDirty ) return ;
        
        _sceneTransformDirty = true ;
        
        var i   : Int  ;
        var len = _children.length ;
        while( i < len )
        {
            
            //TODO: change this.. !
            _children[ i++ ].invalidateSceneTransform() ;
            
        }
        
    }
    
    /**
     * Updates the scene transformation matrix.
     */
    private function updateSceneTransform()
    {
        
        if( _parent )
        {
            
            _sceneTransform.copyFrom( _parent.sceneTransform ) ;
            _sceneTransform.prepend( transform ) ;
            
        }
        else
        {
            
            _sceneTransform.copyFrom( transform ) ;
            
        }
        
        _sceneTransformDirty = false ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    // maybe not the best way to fake bubbling?
    override public function dispatchEvent( event : Event ) : Bool
    {
        
        var ret =  super.dispatchEvent( event ) ;
        
        if( event.bubbles )
        {
            
            if( _parent )
            {
                
                _parent.dispatchEvent( event ) ;
                
            }// if it's scene root
            else if( _scene )
            {
                
                _scene.dispatchEvent( event ) ;
                
            }
            
        }
        
        return ret ;
        
    }
    
    
}